﻿# -*- coding: UTF-8 -*-
"Quelques fonctions utiles permettant de charger un modèle à partir d'un espace de stockage de quelque sorte que ce soit (un fichier, une table de base de données...)"""
import template
from template_file import load_template_source

class ExtendsError(Exception):
    pass

def get_template(template_name):
    """
    Renvoie la version compilée (un objet template.Template) du modèle
    ayant pour nom la chaîne spécifiée; l'héritage des modèles est géré
    de manière récursive.
    """
    return get_template_from_string(load_template_source(template_name))

def get_template_from_string(source):
    """
    Renvoie la version compilée (un objet template.Template) du modèle
    spécifié; l'héritage des modèles est géré de manière récursive.
    """
    return template.Template(source)

def select_template(template_name_list):
    "Cette fonction accepte en argument une liste de noms de modèles et renvoie la version compilée du premier "
    "pouvant être chargé."
    for template_name in template_name_list:
        try:
            return get_template(template_name)
        except template.TemplateDoesNotExist:
            continue
    # Si on arrive jusqu'ici, c'est qu'aucun des modèles n'a pu être chargé
    raise template.TemplateDoesNotExist, ', '.join(template_name_list)

class SuperBlock:
    "Cette classe permet d'utiliser la variable spéciale {{ bloc.super }} à l'intérieur d'un "
    "modèle-fils pour obtenir le contenu d'un bloc du modèle-parent"
    def __init__(self, context, nodelist):
        self.context, self.nodelist = context, nodelist

    def super(self):
        if self.nodelist:
            return self.nodelist.render(self.context)
        else:
            return ''

class BlockNode(template.Node):
    def __init__(self, name, nodelist):
        self.name, self.nodelist = name, nodelist

    def __repr__(self):
        return "<Nœud BlockNode : %s. Contenu : %r>" % (self.name, self.nodelist)

    def render(self, context):
        context.push()
        nodelist = hasattr(self, 'original_node_list') and self.original_node_list or None
        context['block'] = SuperBlock(context, nodelist)
        result = self.nodelist.render(context)
        context.pop()
        return result

class ExtendsNode(template.Node):
    def __init__(self, nodelist, parent_name, parent_name_var, template_dirs=None):
        self.nodelist = nodelist
        self.parent_name, self.parent_name_var = parent_name, parent_name_var
        self.template_dirs = template_dirs

    def get_parent(self, context):
        if self.parent_name_var:
            self.parent_name = template.resolve_variable_with_filters(self.parent_name_var, context)
        parent = self.parent_name
        if not parent:
            error_msg = "Nom de modèle invalide spécifié pour la balise 'extends' : %r." % parent
            if self.parent_name_var:
                error_msg += " Le nom du modèle a été obtenu à partir de la variable %r." % self.parent_name_var
            raise template.TemplateSyntaxError, error_msg
        try:
            return get_template_from_string(load_template_source(parent, self.template_dirs))
        except template.TemplateDoesNotExist:
            raise template.TemplateSyntaxError, "Le modèle %r ne peut pas être étendu car il n'existe pas." % parent

    def render(self, context):
        compiled_parent = self.get_parent(context)
        parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
        parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
        for block_node in self.nodelist.get_nodes_by_type(BlockNode):
            # On cherche un BlockNode dans le modèle-parent portant le même nom qu'un BlockNode de ce modèle et on le
            # remplace si besoin est.
            try:
                parent_block = parent_blocks[block_node.name]
            except KeyError:
                # Le BlockNode actuel n'a pas pu être trouvé dans le modèle-parent mais le bloc pourrait très bien être
                # défini dans le *parent* du modèle-parent, et on ajoute donc le BlockNode actuel à la liste des nœuds-
                # fils du nœud ExtendsNode du modèle-parent pour qu'il soit là aussi vérifié lors de l'appel de la
                # méthode render() sur le modèle-parent.
                if parent_is_child:
                    compiled_parent.nodelist[0].nodelist.append(block_node)
            else:
                # On conserve la liste de nœuds d'origine. Elle servira au nœud BlockNode.
                parent_block.original_node_list = parent_block.nodelist
                parent_block.nodelist = block_node.nodelist
        return compiled_parent.render(context)

def do_block(parser, token):
    """
    Définit un bloc pouvant être surchargé par les modèles-fils.
    """
    bits = token.contents.split()
    if len(bits) != 2:
        raise template.TemplateSyntaxError, "La balise '%s' n'accepte qu'un argument." % bits[0]
    block_name = bits[1]
    # On garde une trace des noms des BlockNode rencontrés précédemment en analysant le modèle, pour éviter
    # d'en avoir en double.
    try:
        if block_name in parser.__loaded_blocks:
            raise template.TemplateSyntaxError, "La balise '%s' de nom '%s' apparaît plus d'une fois dans le modèle." % (bits[0], block_name)
        parser.__loaded_blocks.append(block_name)
    except AttributeError: # parser._loaded_blocks n'est pas encore une liste
        parser.__loaded_blocks = [block_name]
    nodelist = parser.parse(('endblock',))
    parser.delete_first_token()
    return BlockNode(block_name, nodelist)

def do_extends(parser, token):
    """
    Signale au moteur de rendu des modèles que le modèle actuel étend un modèle-parent.

    Cette balise peut être utilisée de deux manières : ``{% extends "base" %}``
    (avec des guillemets) ou ``{% extends variable %}``, où ``variable`` est le
    nom d'une variable ayant pour valeur le nom du modèle à étendre.
    """
    bits = token.contents.split()
    if len(bits) != 2:
        raise template.TemplateSyntaxError, "La balise '%s' n'accepte qu'un argument." % bits[0]
    parent_name, parent_name_var = None, None
    if (bits[1].startswith('"') and bits[1].endswith('"')) or (bits[1].startswith("'") and bits[1].endswith("'")):
        parent_name = bits[1][1:-1]
    else:
        parent_name_var = bits[1]
    nodelist = parser.parse()
    if nodelist.get_nodes_by_type(ExtendsNode):
        raise TemplateSyntaxError, "La balise '%s' ne peut apparaître plus d'une fois dans le même modèle." % bits[0]
    return ExtendsNode(nodelist, parent_name, parent_name_var)

template.register_tag('block', do_block)
template.register_tag('extends', do_extends)
